package org.offsync.async

import java.util.concurrent.atomic.AtomicReference
import java.nio.channels.SelectionKey
import java.nio.channels.ClosedChannelException
import org.offsync.util.DisposedException
import org.offsync.util.DisposableBase
import org.offsync.rich._

class AsyncInterest(val selectable: AsyncSelectable, val op: Int) extends DisposableBase {
  private val slot = new AtomicReference[AsyncInterestAction]

  def process(key: SelectionKey, interestOps: Int)(processCompletion: () => Unit): Unit = {
    if ((interestOps & op) != 0) {
      val function = slot.getAndSet(null)

      if (function != null) {
        function(key, processCompletion)
      }
    }
  }

  def fill(registeredKey: SelectionKey)(function: (() => Unit) => Unit): Unit = {
    val registered = slot.compareAndSet(null, new AsyncInterestAction(registeredKey) {
      override def apply(readyKey: SelectionKey, interestActionCompletion: () => Unit): Unit = {
        function(interestActionCompletion)
      }
    })

    assert(registered)
  }

  def isRegisteredWith(selector: AsyncSelector): Boolean = {
    val op = slot.get()

    if (op == null) {
      return false
    }

    if (op.key == null) {
      return false
    }

    return op.key.selector() == selector.selector
  }

  def register(selector: AsyncSelector)(task: (() => Unit) => Unit): Unit = {
    selectable.register(selector, op) { registerCompletion =>
      try {
        val key = registerCompletion()
        key match {
          case Some(selectionKey) => fill(selectionKey)(task)
          case None => println("Missing selection key")
        }
      } catch {
        case e: DisposedException => {
          e.printStackTrace(System.out)
          task { () => throw new DisposedException(e) }
        }
        case e => {
          task { () => e.wrap() }
        }
      }
      true
    }
  }

  def onReady(selector: AsyncSelector)(task: (() => Unit) => Unit): Unit = {
    try {
      selector.execute {
        val onceOnlyTask = Functions.onceOnly(task)
        try {
          register(selector)(onceOnlyTask)
        } catch {
          case e: ClosedChannelException => {
            e.printStackTrace(System.out)
            onceOnlyTask { () => throw new DisposedException(e) }
          }
          case e => {
            e.printStackTrace(System.out)
            onceOnlyTask { () => throw e.wrap() }
          }
        }
      }
    } catch {
      case e: Exception => {
        task { () => throw e.wrap() }
      }
    }
  }
  
  protected override def onDispose(): Unit = {
    try {
      process(null, op) { () =>
        throw new DisposedException()
      }
    } catch {
      case e => e.printStackTrace(System.err)
    } finally {
      super.onDispose()
    }
  }
}
